//
//  ZLAudioEncoder.m
//  Video
//
//  Created by os on 2021/4/13.
//

#import "ZLAudioEncoder.h"
#import <AudioToolBox/AudioToolbox.h>
#import "ZLAVConfig.h"

@interface ZLAudioEncoder ()

@property (nonatomic, strong) dispatch_queue_t encoderQueue;
@property (nonatomic, strong) dispatch_queue_t callbackQueue;

/// 音频转换器对象
@property (nonatomic, unsafe_unretained) AudioConverterRef audioConverter;
/// PCM 缓存区
@property (nonatomic) char *pcmBuffer;
/// PCM 缓存区大小
@property (nonatomic) size_t pcmBufferSize;

@end

@implementation ZLAudioEncoder

/// 编码器回调函数
static OSStatus accEncodeInputDataProc(AudioConverterRef inAudioConverter,
                                       UInt32 *ioNumberDataPackets,
                                       AudioBufferList *ioData,
                                       AudioStreamPacketDescription **outDataPacketDescription,
                                       void *inUserData) {
    
    ZLAudioEncoder *aacEncoder = (__bridge ZLAudioEncoder *)(inUserData);
    
    // 判断 pcmBuffersize 大小
    if (!aacEncoder.pcmBufferSize) {
        *ioNumberDataPackets = 0;
        return -1;
    }
    
    // 填充
    ioData->mBuffers[0].mData = aacEncoder.pcmBuffer;
    ioData->mBuffers[0].mDataByteSize = (UInt32) aacEncoder.pcmBufferSize;
    ioData->mBuffers[0].mNumberChannels = (UInt32)aacEncoder.config.channelCount;
    
    // 填充完毕，则清空数据
    aacEncoder.pcmBufferSize = 0;
    *ioNumberDataPackets = 1;
    
    return noErr;
}

#pragma mark - initConfig

- (instancetype)initWithConfig:(ZLAudioConfig *)config {
    if (self = [super init]) {
        // 音频编码队列
        _encoderQueue = dispatch_queue_create("aac hard encoder queue", DISPATCH_QUEUE_SERIAL);
        // 音频回调队列
        _callbackQueue = dispatch_queue_create("aac hard encoder callback queue", DISPATCH_QUEUE_SERIAL);
        // 音频转换器
        _audioConverter = NULL;
        _pcmBufferSize = 0;
        _pcmBuffer = NULL;
        _config = config;
        
        if (_config == nil) {
            _config = [[ZLAudioConfig alloc] init];
        }
    }
    return self;
}

// 音频编码（当 AVFoundation 捕获到音频内容之后）
- (void)encodeAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    
    CFRetain(sampleBuffer);
    
    // 判断音频转换器是否创建成功，如果未创建成功，则配置音频编码参数且创建转换器
    if (!_audioConverter) {
        [self setupEncoderWithSampleBuffer:sampleBuffer];
    }
    
    // 音频编码异步队列
    dispatch_async(_encoderQueue, ^{
        // 获取 CMBlockBuffer，这里面保存了 PCM 数据
        CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
        CFRetain(blockBuffer);
        
        // 获取 BlockBuffer 中音频数据大小以及音频数据地址
        OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &_pcmBufferSize, &_pcmBuffer);
        NSError *error = nil;
        if (status != noErr) {
            error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
            NSLog(@"Error: AAC encode get data point error: %@", error);
            return;
        }
        
        // 开辟大小为 _pcmBufferSize 的 pcm 内存空间
        uint8_t *pcmBuffer = malloc(_pcmBufferSize);
        memset(pcmBuffer, 0, _pcmBufferSize);
        
        // 输出 buffer
        // 将 pcmBuffer 数据填充到 outAudioBufferList 对象中
        AudioBufferList outAudioBufferList = {0};
        outAudioBufferList.mNumberBuffers = 1;
        outAudioBufferList.mBuffers[0].mNumberChannels = (UInt32) _config.channelCount;
        outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32) _pcmBufferSize;
        outAudioBufferList.mBuffers[0].mData = pcmBuffer;
        
        // 输出包大小为 0
        UInt32 outputDataPacketSize = 1;
        
        // 配置填充函数，获取输出函数
        // 转换由输入回调函数提供的数据
        // 参数1: inAudioConverter 音频转换器
        // 参数2: inInputDataProc 回调函数.提供要转换的音频数据的回调函数。当转换器准备好接受新的输入数据时，会重复调用此回调.
        // 参数3: inInputDataProcUserData,self
        // 参数4: ioOutputDataPacketSize,输出缓冲区的大小
        // 参数5: outOutputData,需要转换的音频数据
        // 参数6: outPacketDescription,输出包信息
        status = AudioConverterFillComplexBuffer(_audioConverter, accEncodeInputDataProc, (__bridge void * _Nullable)(self), &outputDataPacketSize, &outAudioBufferList, NULL);
        if (status != noErr) {
            error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
        } else {
            // 获取数据
            NSData *rawAAC = [NSData dataWithBytes:outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];
            // 释放 pcmBuffer
            free(pcmBuffer);
            
            // 写入文件时, 必须添加ADTS头，如果是想要获取裸流数据是，忽略ADTS
            /**
             NSData *adtsHeader = [self adtsDataForPacketLength:rawAAC.length];
             NSMutableData *fullData = [NSMutableData dataWithCapacity:adtsHeader.length + rawAAC.length];
             [fullData appendData:adtsHeader];
             [fullData appendData:rawAAC];
             */
            
            dispatch_async(_callbackQueue, ^{
                [_delegate audioEncodeCallback:rawAAC];
            });
        }
        
        CFRelease(blockBuffer);
        CFRelease(sampleBuffer);
        
        if (error) {
            NSLog(@"Error: AAC 硬编码失败 %@", error);
        }
    });
}

// 配置音频编码参数
- (void)setupEncoderWithSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    
    // 获取输入参数
    AudioStreamBasicDescription inputAudioDes = *CMAudioFormatDescriptionGetStreamBasicDescription(CMSampleBufferGetFormatDescription(sampleBuffer));
    
    // 设置输出参数
    AudioStreamBasicDescription outputAudioDes = { 0 };
    outputAudioDes.mSampleRate = (Float64)_config.sampleRate;           // 采样率
    outputAudioDes.mFormatID = kAudioFormatMPEG4AAC;                    // 输出格式
    outputAudioDes.mFormatFlags = kMPEG4Object_AAC_LC;                  // 如果设为0 代表无损编码
    outputAudioDes.mBytesPerPacket = 0;                                 // 自己确定每个 packet 大小
    outputAudioDes.mFramesPerPacket = 1024;                             // 每一个 packet 帧数 AAC-1024;
    outputAudioDes.mBytesPerFrame = 0;                                  // 每一帧大小
    outputAudioDes.mChannelsPerFrame = (uint32_t)_config.channelCount;  // 输出声道数
    outputAudioDes.mBitsPerChannel = 0;                                 // 数据帧中每个通道的采样位数
    outputAudioDes.mReserved = 0;                                       // 对齐方式 0 (8字节对齐)
    
    // 填充输出相关信息
    UInt32 outDesSize = sizeof(outputAudioDes);
    AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &outDesSize, &_audioConverter);
    
    // 获取编码器的描述信息（只能传入software）
    AudioClassDescription *audioClassDesc = [self getAudioClassDescriptionWithType:outputAudioDes.mFormatID fromManufacture:kAppleSoftwareAudioCodecManufacturer];
    
    /// 创建 converter
    /// 参数1：输入音频格式描述
    /// 参数2：输出音频格式描述
    /// 参数3：class desc的数量
    /// 参数4：class desc
    /// 参数5：创建的解码器
    OSStatus status = AudioConverterNewSpecific(&inputAudioDes, &outputAudioDes, 1, audioClassDesc, &_audioConverter);
    if (status != noErr) {
        NSLog(@"Error: 硬编码 AAC 创建失败， status = %d", (int)status);
        return;
    }
    
    // 设置编码器质量
    /*
     kAudioConverterQuality_Max                              = 0x7F,
     kAudioConverterQuality_High                             = 0x60,
     kAudioConverterQuality_Medium                           = 0x40,
     kAudioConverterQuality_Low                              = 0x20,
     kAudioConverterQuality_Min                              = 0
     */
    UInt32 temp = kAudioConverterQuality_High;
    // 编解码器的呈现质量
    AudioConverterSetProperty(_audioConverter, kAudioConverterCodecQuality, sizeof(temp), &temp);
    
    // 设置比特率
    uint32_t audioBitrate = (uint32_t)self.config.bitrate;
    status = AudioConverterSetProperty(_audioConverter, kAudioConverterEncodeBitRate, sizeof(audioBitrate), &audioBitrate);
    if (status != noErr) {
        NSLog(@"Error: 硬编码AAC 设置比特率失败");
    }
    
}

/// 获取编码器类型描述
/// @param type 类型
/// @param manufacture 制造
- (AudioClassDescription *)getAudioClassDescriptionWithType:(AudioFormatID)type fromManufacture:(uint32_t)manufacture {
    
    static AudioClassDescription desc;
    UInt32 encoderSpecific = type;
    
    // 获取满足 AAC 编码器的总大小
    UInt32 size;
    
    /// 获取编码器类型描述
    /// 参数1：编码器类型
    /// 参数2：类型描述大小
    /// 参数3：类型描述
    /// 参数4：大小
    OSStatus status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders, sizeof(encoderSpecific), &encoderSpecific, &size);
    if (status != noErr) {
        NSLog(@"Error: 硬编码 AAC get info 失败，status = %d", (int)status);
        return nil;
    }
    
    // 计算 aac 编码器的个数
    unsigned int count = size / sizeof(AudioClassDescription);
    // 创建一个包含 count 个编码器的数组
    AudioClassDescription description[count];
    // 将满足 aac 编码的编码器的信息写入数组
    status = AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(encoderSpecific), &encoderSpecific, &size, &description);
    if (status != noErr) {
        NSLog(@"Error: 硬编码 AAC get property 失败，status = %d", (int)status);
        return nil;
    }
    
    for (unsigned int i = 0; i < count; i++) {
        if (type == description[i].mSubType && manufacture == description[i].mManufacturer) {
            desc = description[i];
            return &desc;
        }
    }
    return nil;
}

/// AAC ADTS 头
- (NSData *)adtsDataForPacketLength:(NSUInteger)packetLength {
    int adtsLength = 7;
    char *packet = malloc(sizeof(char) * adtsLength);
    int profile = 2; // AAC LC
    int freqIdx = 4; // 3：48000 Hz  4：44.1KHz   8: 16000 Hz     11: 8000 Hz
    int chanCfg = 1; // MPEG-4 Audio Channel Configuration. 1 Channel front-center
    NSUInteger fullLength = adtsLength + packetLength;
    // fill in ADTS data
    packet[0] = (char)0xFF; // 1111 1111      = syncword
    packet[1] = (char)0xF9; // 1111 1001      = syncword MPEG-2 Layer CRC
    packet[2] = (char)(((profile-1)<<6) + (freqIdx<<2) + (chanCfg>>2));
    packet[3] = (char)(((chanCfg&3)<<6) + (fullLength>>11));
    packet[4] = (char)((fullLength&0x7FF) >> 3);
    packet[5] = (char)(((fullLength&7)<<5) + 0x1F);
    packet[6] = (char)0xFC;
    
    NSData *data = [NSData dataWithBytesNoCopy:packet length:adtsLength freeWhenDone:YES];
    return data;
}

@end
